package com.googlecode.functionalcollections;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;



public abstract class FunctionalIterables {

	public static <F> FunctionalIterable<F> make(Iterable<F> iterable) {
		return new StandardFunctionalIterable<F>(iterable);
	}
	
	public static <F> FunctionalIterable<F> make(F... elements) {
		return new StandardFunctionalIterable<F>(Lists.newArrayList(elements));
	}
	
	public static <F> FunctionalIterable<F> create() {
		return make(new ArrayList<F>());
	}
	
	private static final class StandardFunctionalIterable<F> implements FunctionalIterable<F> {

		private final Iterable<F> iterable;

		public StandardFunctionalIterable(Iterable<F> iterable) {
			this.iterable = iterable;
		}

		public boolean all(Predicate<? super F> predicate) {
			return Iterables.all(iterable, predicate);
		}

		public boolean any(Predicate<? super F> predicate) {
			return Iterables.any(iterable, predicate);
		}

		public FunctionalIterable<F> each(Block<? super F> block) {
			Iterator<? extends F> iterator = iterable.iterator();
			
			while (iterator.hasNext()) {
				block.apply(iterator.next());
			}
			
			return this;
		}

		public FunctionalIterable<F> filter(Predicate<? super F> predicate) {
			return make(Iterables.filter(iterable, predicate));
		}
		
		public FunctionalIterable<F> select(Predicate<? super F> predicate) {
			return filter(predicate);
		}

		public F find(Predicate<? super F> predicate) {
			return Iterables.find(iterable, predicate);
		}

		public F detect(Predicate<? super F> predicate) {
			Iterator<F> iterator = filter(predicate).iterator();
			return iterator.hasNext() ? iterator.next() : null;
		}
		
		public <T> FunctionalIterable<T> transform(Function<? super F, ? extends T> function) {
			return make(Iterables.transform(iterable, function));
		}
		
		public <T> FunctionalIterable<T> map(Function<? super F, ? extends T> function) {
			return transform(function);
		}

		public Iterator<F> iterator() {
			return iterable.iterator();
		}

		public <M> M inject(M initial, Injector<M, F> injector) {
			M memo = initial;
			for (F each : iterable) {
				memo = injector.apply(memo, each);
			}
			return memo;
		}

		public boolean contains(Object element) {
			return Iterables.contains(iterable, element);
		}

		public FunctionalIterable<F> concat(Iterable<? extends F> target) {
			return make(Iterables.concat(iterable, target));
		}
		
		public FunctionalIterable<F> concat(F... target) {
			return concat(Lists.newArrayList(target));
		}

		public FunctionalIterable<F> cycle() {
			return make(Iterables.cycle(iterable));
		}

		public boolean elementsEqual(Iterable<?> target) {
			return Iterables.elementsEqual(iterable, target);
		}

		public int frequency(Object element) {
			return Iterables.frequency(iterable, element);
		}

		public F get(int position) {
			return Iterables.get(iterable, position);
		}

		public F getLast() {
			return Iterables.getLast(iterable);
		}

		public F getOnlyElement() {
			return Iterables.getOnlyElement(iterable);
		}

		public F getOnlyElement(F defaultValue) {
			return Iterables.getOnlyElement(iterable, defaultValue);
		}

		public boolean isEmpty() {
			return Iterables.isEmpty(iterable);
		}

		public FunctionalIterable<List<F>> paddedPartition(int size) {
			return make(Iterables.paddedPartition(iterable, size));
		}

		public FunctionalIterable<List<F>> partition(int size) {
			return make(Iterables.partition(iterable, size));
		}

		public boolean removeAll(Collection<?> elementsToRemove) {
			return Iterables.removeAll(iterable, elementsToRemove);
		}

		public boolean retainAll(Collection<?> elementsToRetain) {
			return Iterables.retainAll(iterable, elementsToRetain);
		}

		public int size() {
			return Iterables.size(iterable);
		}

		public F[] toArray(Class<F> type) {
			return Iterables.toArray(iterable, type);
		}

		public Collection<F> toCollection() {
			if (Collection.class.isInstance(iterable)) {
				return (Collection<F>) iterable;
			}
			return Lists.newArrayList(iterable);
		}

		public List<F> toList() {
			if (List.class.isInstance(iterable)) {
				return (List<F>) iterable;
			}
			return Lists.newArrayList(iterable);
		}

		public Set<F> toSet() {
			if (Set.class.isInstance(iterable)) {
				return (Set<F>) iterable;
			}
			return Sets.newLinkedHashSet(iterable);
		}
		
		public FunctionalIterable<F> uniq() {
			return make(Sets.newLinkedHashSet(iterable));
		}

		public FunctionalIterable<F> reverse() {
			return make(Iterables.reverse(Lists.newArrayList(iterable)));
		}

		public Iterator<F> messagedIterator(final String messageOnLast) {
			final Iterator<F> iterator = iterable.iterator();
			
			return new Iterator<F>() {

				public boolean hasNext() {
					return iterator.hasNext();
				}

				public F next() {
					if (!hasNext()) {
						throw new NoSuchElementException(messageOnLast);
					}
					return iterator.next();
				}

				public void remove() {
					iterator.remove();
				}
			};
		}

		@Override
		public String toString() {
			return Iterables.toString(iterable);
		}


//		public FunctionalIterable<F> flatten() {
//			// TODO Auto-generated method stub
//			throw new UnsupportedOperationException("not yet implement.");
//		}

	
	}
	

	
	
}
